// Copyright (c) 2019 Presto Labs Pte. Ltd.
// Author: donggu

#include "coin2/flow/symbol_group.h"

#include <algorithm>
#include <string>
#include <unordered_map>
#include <vector>

#include <toml.hpp>

#include "coin2/base/time.h"
#include "coin2/exchange/base/market/enums.h"
#include "coin2/exchange/factory/symbology/product.h"
#include "coin2/exchange/huobi_futures/symbology/product.h"
#include "coin2/exchange/okex_futures/symbology/product.h"

namespace coin2::flow {

SymbolGroupConfig::SymbolGroupConfig(const std::experimental::filesystem::path& toml_path) {
  const auto data = toml::parse(toml_path);
  auto config = toml::get<
      std::unordered_map<std::string, std::unordered_map<std::string, std::vector<std::string>>>>(
      data);

  for (auto& exchange_it : config) {
    auto mea = MarketExchangeApi::FromSnakeStringNoThrow(exchange_it.first);
    if (!mea.has_value()) {
      continue;
    }

    SymbolGroupMap map;
    SymbolTable table;
    for (auto it : exchange_it.second) {
      // a: ["dd", "ee"]
      char group = it.first[0];
      table[group].clear();
      for (auto norm_symbol : it.second) {
        map[norm_symbol] = group;
        table[group].push_back(norm_symbol);
      }
    }
    map_[mea.value()] = map;
    table_[mea.value()] = table;
  }
}

std::vector<char> SymbolGroupConfig::GetGroups(const MarketExchangeApi& mea) const
{
  auto it = table_.find(mea);
  if (it == table_.end()) {
    throw std::runtime_error("[SymbolGroupConfig] cannot find mea: " + mea.SnakeString());
  }

  std::vector<char> res;
  const auto& groups = it->second;
  std::transform(
    groups.begin(),
    groups.end(),
    std::back_inserter(res), [](const auto &p){ return p.first; });

  return res;
}

char SymbolGroupConfig::GetGroupForRelativeNorm(
    const MarketExchangeApi& mea,
    const std::string& symbol) const {
  using MarketType = coin::proto::MarketType;
  using ExchangeType = coin::proto::ExchangeType;
  auto it = map_.find(mea);
  if (it == map_.end()) {
    throw std::runtime_error("[SymbolGroupConfig] cannot find topic_group: " + mea.SnakeString());
  }

  char g = 'z';
  auto it2 = it->second.find(symbol);
  if (it2 == it->second.end()) {
    if (mea == MarketExchangeApi{MarketType::Options, ExchangeType::Deribit, "v2"}) {
      std::string symbol_tmpl = symbol;
      size_t last_dot = symbol_tmpl.rfind(".");
      symbol_tmpl.replace(last_dot, symbol_tmpl.size() - last_dot, ".ALL");
      auto it3 = it->second.find(symbol_tmpl);
      if (it3 == it->second.end()) {
        throw std::runtime_error(
            "[SymbolGroupConfig] cannot find symbol: " + symbol + " in " + mea.SnakeString());
      } else {
        g = it3->second;
      }
    } else if (mea == MarketExchangeApi{MarketType::Spot, ExchangeType::Coingecko, "v3"}) {
      g = 'a';
    } else {
      if (symbol.find("BCHABC") != std::string::npos) {
        std::string symbol_tmpl = symbol;
        boost::replace_all(symbol_tmpl, "BCHABC", "BCHN");
        auto it3 = it->second.find(symbol_tmpl);
        if (it3 == it->second.end()) {
          throw std::runtime_error("[SymbolGroupConfig] cannot find symbol: " +
                                   symbol + " in " + mea.SnakeString());
        } else {
          g = it3->second;
        }
      } else {
        throw std::runtime_error("[SymbolGroupConfig] cannot find symbol: " +
                                 symbol + " in " + mea.SnakeString());
      }
    }
  } else {
    g = it2->second;
  }
  return g;
}
const std::vector<std::string>& SymbolGroupConfig::GetRelativeNormSymbols(
    const MarketExchangeApi& mea,
    char group) const {
  return table_.at(mea).at(group);
}
const std::vector<std::string>& SymbolGroupConfig::GetAllRelativeNormSymbols(
    const MarketExchangeApi& mea) const {
  if (all_symbols_.empty()) {
    auto groups = GetGroups(mea);
    for (const auto& group : groups) {
      auto groupvec = table_.at(mea).at(group);
      all_symbols_.insert(all_symbols_.end(), groupvec.begin(), groupvec.end());
    }
  }
  return all_symbols_;
}

std::unique_ptr<SymbolGroupConfig> SymbolGroupConfig::config_;

}  // namespace coin2::flow
